
package ploy.compiler;

import java.util.*;

import org.objectweb.asm.*;
import org.objectweb.asm.util.*;
import org.objectweb.asm.commons.*;

import ploy.lang.*;

/**
 * A var location represents information about where a Ploy variable is
 * located in bytecode.
 */
abstract class VarLocation {
    
    /**
     * Generate bytecode for this variable, putting its value on the stack.
     */
    public abstract FrameSize generateBytecode( MethodVisitor mv );

    /**
     * Does this variable need to be stored in a closure for enclosed lambdas?
     */
    public abstract boolean needsLocalStorage( );

}

/**
 * CurrentClosureLocation represents a variable located in the closure
 * of the enclosing function.
 */
class CurrentClosureVarLocation extends VarLocation {

    private String closureClass;
    private String field;

    public CurrentClosureVarLocation( String c, String f ) {
        closureClass = c;
        field = f;
    }

    /**
     * Generating bytecode takes the value from the "this" object.
     */
    public FrameSize generateBytecode( MethodVisitor mv ) {
        mv.visitVarInsn(Opcodes.ALOAD, 0);
        mv.visitFieldInsn(Opcodes.GETFIELD, closureClass, field, 
			  DynamicObject.jvmType);
        return new FrameSize( 1, 0 );
    }

    public boolean needsLocalStorage( ) { return true; }

}

/**
 * An ArgumentLocation represents a variable that is an argument to the current
 * function.
 */
class ArgumentLocation extends VarLocation {
    private int argNum;

    public ArgumentLocation( int a ) {
        argNum = a;
    }

    /**
     * Generating bytecode takes the JVM frame variable and puts it on the
     * stack.
     */
    public FrameSize generateBytecode( MethodVisitor mv ) {
        mv.visitVarInsn(Opcodes.ALOAD, argNum);
        return new FrameSize( 1, 0 );
    }

    public boolean needsLocalStorage( ) { return true; }

}


/**
 * A GlobalVarLocation represents a global variable.
 */
class GlobalVarLocation extends VarLocation {
    private String varName;
    public static final String symbolType = "(Ljava/lang/String;)V";

    public GlobalVarLocation( String n ) {
	varName = n;
    }

    /**
     * To lookup a global variable, we call the static function "lookupGlobal"
     * on class "ploy.lang.Globals" with the appropriate symbol argument.
     */
    public FrameSize generateBytecode( MethodVisitor mv ) {
	mv.visitTypeInsn(Opcodes.NEW, Symbol.jvmClass);
	mv.visitInsn(Opcodes.DUP);
	mv.visitLdcInsn( varName );
	mv.visitMethodInsn(Opcodes.INVOKESPECIAL, Symbol.jvmClass,
			   "<init>", symbolType);
	mv.visitMethodInsn(Opcodes.INVOKESTATIC, "ploy/lang/Globals",
			   "lookupGlobal", "(" + Symbol.jvmType + ")" +
			   DynamicObject.jvmType);
        return new FrameSize( 3, 0 );
    }

    public boolean needsLocalStorage( ) { return false; }

}

/**
 * A VarLocationTable is a mapping from symbols to variable locations.
 * If a symbol is not found, it returns a global variable location.
 */
class VarLocationTable {
 
    protected Hashtable<Symbol, VarLocation> table;

    public VarLocationTable( ) {
	table = new Hashtable<Symbol, VarLocation>();
    }

    private VarLocationTable( Hashtable<Symbol, VarLocation> t ) {
	table = t;
    }
    
    public void put( Symbol s, VarLocation l ) {
	table.put( s, l );
    }

    public VarLocation get( Symbol s ) {
	VarLocation l = table.get( s );
	if (l != null) return l;
	return new GlobalVarLocation( s.toString() );
    }

    public VarLocationTable clone( ) {
	return new VarLocationTable( (Hashtable<Symbol, VarLocation>)
				     table.clone( ) );
    }
				      
   
}